#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "channel.h"
#include "menudialog.h"

#include <QSettings>
#include <QtConcurrentRun>
#include <QFutureWatcher>
#include <QNetworkReply>
#include <QAuthenticator>
#include <QKeyEvent>
#include <QMessageBox>
#include <QPixmapCache>
#include <QTextEdit>
#include <QFile>
#include <QDir>

Q_DECLARE_METATYPE(QNetworkReply::NetworkError);

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow()),
      currentChannel(-1),
      authSent(false)
#ifdef Q_OS_MAC
      , remote(this)
#endif
{
    qDebug() << "Main thread id " << QThread::currentThreadId();

    QSettings settings;
    ensureDefaultSettings();
    ui->setupUi(this);
    ui->viewStack->addWidget(player.widget());

    // install custom stylesheets
    QStringList sheets;
    sheets << ":/ui/styles/common-frameless.qss"
           << ":/ui/HD-1080"
           << ":/ui/Dark";
    loadStyleSheets(sheets);
    
    // set main window focus and size
    restoreGeometry(settings.value("ui/geometry").toByteArray());
    ui->programTable->setFocus();
    connect(ui->programTable, SIGNAL(activated(const QModelIndex&)), SLOT(showProgramMenu()));
    updateClock();
    
    // udpate channel list
    channels = new ChannelList(this);
    connect(channels, SIGNAL(updated(bool)), SLOT(channelsUpdated(bool)));
    connect(channels, SIGNAL(progressInfo(const QString&, int, int)),
            SLOT(showProgressInfo(const QString&, int, int)));

    // instantiate video player
//#ifndef Q_OS_MAC
//    player = new Player(ui->viewStack);
//    player->connectAuthenticator(this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*)));
//    connect(player, SIGNAL(stopped()), SLOT(playbackFinished()));
//    ui->viewStack->addWidget(player); // yields ownership
//#endif

    // refresh program info when thumbnails have been fetched
    connect(&http, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
            this, SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*)));
    connect(&delayedThumbnailLoad, SIGNAL(timeout()), SLOT(loadCurrentThumbnail()));
    delayedThumbnailLoad.setSingleShot(true);

    // clock updater
    connect(&clockUpdater, SIGNAL(timeout()), SLOT(updateClock()));
    clockUpdater.start(60*1000); // update every minute

    // actions
    connect(ui->actionQuit, SIGNAL(triggered()), SLOT(close())); // quits the app
    connect(ui->actionPlay, SIGNAL(triggered()), SLOT(startPlayback()));
    connect(ui->actionToggleFullScreen, SIGNAL(triggered()), SLOT(toggleFullScreen()));
    connect(ui->actionShowMainMenu, SIGNAL(triggered()), SLOT(showMainMenu()));
    connect(ui->actionShowProgramMenu, SIGNAL(triggered()), SLOT(showProgramMenu()));
    connect(ui->actionShowSettingsMenu, SIGNAL(triggered()), SLOT(showSettingsMenu()));

    // start updating channels in next iteration
    QTimer::singleShot(0, this, SLOT(updateChannelsIfNecessary()));

#ifdef Q_OS_MAC
    // start listening the remote control
    remote.startListening();
#endif
}

MainWindow::~MainWindow()
{
    delayedThumbnailLoad.stop();
    clockUpdater.stop();
    channels->cancel();
    delete ui; // not parent
}

void MainWindow::loadStyleSheets(const QStringList& sheets)
{
    QByteArray data;
    foreach (QString sheet, sheets) {
        QFile file(sheet);
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            qDebug() << "Loading stylesheet " << sheet;
            data.append(file.readAll());
            data.append("\n"); // just to be sure
        }
    }
    //qDebug() << data;
    qApp->setStyleSheet(data);
}

Program* MainWindow::currentProgram() const
{
    int row = ui->programTable->currentIndex().row();
    Program* p = 0;
    if (currentChannel >= 0 && row >= 0) {
        Channel* c = channels->at(currentChannel);
        if (c && row < c->programs.count())
            p = c->programs[row];
    }
    return p;
}

void MainWindow::setCurrentChannel(int channel)
{
    int selectRow = -1;
    qDebug() << "Switching channel from " << currentChannel << " to " << channel;

    if (channel < 0 || channel >= channels->count()) {
        ui->programTable->setModel(0);
        showMessage(tr("No channel selected"));
        return;
    }

    Program* p = currentProgram();
    if (p) {
        // select the program that is ongoing at the same time
        QList<Program*>& progs = channels->at(channel)->programs;
        for (selectRow = 0; selectRow < progs.count()-1; ++selectRow) {
            qint64 diffToPrev = progs[selectRow]->diff(*p);
            qint64 diffToNext = p->diff(*progs[selectRow+1]);
            if (diffToPrev >= 0 && diffToNext > 0) {
                if (diffToNext < diffToPrev)
                    selectRow++;
                break;
            }
        }
    }
                           
    currentChannel = channel;
    Channel* c = channels->at(channel);
    ui->rightSideStack->setCurrentWidget(ui->programTablePage);
    ui->programTable->setModel(channels->at(channel));
    ui->programTable->resizeColumnsToContents();
    ui->programTable->resizeRowsToContents();
    ui->programTable->showColumn(0); // make sure time column is not hidden
    ui->channelInfo->setText(c->title);

    // connection selection signal and do the initial row selection
    connect(ui->programTable->selectionModel(),
            SIGNAL(currentRowChanged(const QModelIndex&, const QModelIndex&)),
            this, SLOT(showFocusedProgram()));
    if (selectRow < 0)
        selectRow = c->programs.count() - 1; // select last (=latest) program
    ui->programTable->selectRow(selectRow);
}

void MainWindow::ensureDefaultSettings(bool reset)
{
    QSettings settings;
    if (settings.value("feed/baseUrl").isNull() || reset)
        settings.setValue("feed/baseUrl", "http://alpha.tvkaista.fi/feed/channels/");
    if (settings.value("feed/username").isNull() || reset)
        settings.setValue("feed/username", "");
    if (settings.value("feed/password").isNull() || reset)
        settings.setValue("feed/password", "");
    if (settings.value("feed/minUpdateInterval").isNull() || reset)
        settings.setValue("feed/minUpdateInterval", 15); // minutes
    if (settings.value("feed/thumbnailLoadDelay").isNull() || reset)
        settings.setValue("feed/thumbnailLoadDelay", 300); // ms

    if (settings.value("ui/overscan/left").isNull() || reset)
        settings.setValue("ui/overscan/left", 50);
    if (settings.value("ui/overscan/top").isNull() || reset)
        settings.setValue("ui/overscan/top", 30);
    if (settings.value("ui/overscan/bottom").isNull() || reset)
        settings.setValue("ui/overscan/bottom", 30);
    if (settings.value("ui/overscan/right").isNull() || reset)
        settings.setValue("ui/overscan/right", 50);
    if (settings.value("ui/maxDialogCover").isNull() || reset)
        settings.setValue("ui/maxDialogCover", 0.85); // % of parent widget width/height
    if (settings.value("ui/thumbnailWidth").isNull() || reset)
        settings.setValue("ui/thumbnailWidth", 0.30); // % of program description width
}

void MainWindow::updateChannelsIfNecessary()
{
    updateChannels(false);
}

void MainWindow::updateChannels(bool force)
{
    // start update in separate thread
    ui->programTable->setModel(0);
    authSent = false;
    channels->update(force);
}

void MainWindow::channelsUpdated(bool success)
{
    qDebug() << "Channel update done, " << channels->count()
             << " channels found, success=" << success;
    if (success && channels->count() > 0)
        setCurrentChannel(qMin(0, qMax(currentChannel, channels->count()-1)));
    else
        showMessage(tr("No channel data"));
}

void MainWindow::showFocusedProgram()
{
    QSettings settings;

    // stop loading thumbnails for now
    delayedThumbnailLoad.stop();

    Program* p = currentProgram();
    if (!p)
        return;

    // update current date in calendar
    ui->programCalendar->setSelectedDate(p->pubDate.toLocalTime().date());

    // update info text with or without thumbnail
    QPixmap thumbnail;
    if (QPixmapCache::find(p->thumbnail.toString(), thumbnail)) {
        // thumbnail exists, update info box with pixmap
        ui->programDescription->setWordWrapMode(QTextOption::WordWrap);
        ui->programDescription->document()->addResource(
                QTextDocument::ImageResource, p->thumbnail, thumbnail);
        QString tmpl("<h3>%0</h3><p><img src=\"%2\" width=\"%3\" align=\"right\"/>%1</p>");
        double scaler = settings.value("ui/thumbnailWidth").toDouble();
        ui->programDescription->setText(tmpl.arg(p->title)
                                            .arg(p->description)
                                            .arg(p->thumbnail.toString())
                                            .arg(ui->programDescription->width() * scaler));
    }
    else {
        // no thumbnail (yet), check if we should try to fetch it
        if (!p->thumbnail.isEmpty())
            delayedThumbnailLoad.start(settings.value("feed/thumbnailLoadDelay").toInt());
        // update text without thumbnail (for now)
        QString tmpl("<h3>%0</h3><p>%1</p>");
        ui->programDescription->setText(tmpl.arg(p->title).arg(p->description));
    }
}

void MainWindow::keyPressEvent(QKeyEvent* event)
{
    switch (event->key()) {
    case Qt::Key_Left:
        if (currentChannel > 0) {
            setCurrentChannel(currentChannel - 1);
            return;
        }
        break;

    case Qt::Key_Right:
        if (currentChannel < channels->count() - 1) {
            setCurrentChannel(currentChannel + 1);
            return;
        }
        break;

    case Qt::Key_F5:
        updateChannels(event->modifiers() & Qt::ShiftModifier);
        return;

    case Qt::Key_F1:
        toggleFullScreen();
        return;

    case Qt::Key_F2:
        showAllSettingsMenu();
        return;

    case Qt::Key_Enter:
    case Qt::Key_Return:
    case Qt::Key_Play:
    case Qt::Key_MediaPlay:
        showProgramMenu();
        return;

    case Qt::Key_Escape:
        showMainMenu();
        return;
    }

    QMainWindow::keyPressEvent(event);
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    QSettings settings;
    settings.setValue("ui/geometry", saveGeometry());
    QMainWindow::closeEvent(event);
}

void MainWindow::changeEvent(QEvent* event)
{
    QMainWindow::changeEvent(event);

    if (event->type() == QEvent::WindowStateChange) {
        // check if we need overscan compensation
        if (isFullScreen() || isMaximized()) {
            QSettings s;
            setContentsMargins(s.value("ui/overscan/left").toInt(),
                               s.value("ui/overscan/top").toInt(),
                               s.value("ui/overscan/right").toInt(),
                               s.value("ui/overscan/bottom").toInt());
        }
        else
            setContentsMargins(0, 0, 0, 0);
    }
}

void MainWindow::authenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator)
{
    qDebug() << "Authentication requested for realm " << authenticator->realm();

    QSettings s;
    QString username = s.value("feed/username").toString();
    QString password = s.value("feed/password").toString();
    if (!authSent && !username.isEmpty() && !password.isEmpty()) {
        authenticator->setUser(username);
        authenticator->setPassword(password);
        authSent = true;
    }
    else {
        qDebug() << "Authentication apparently failed, aborting";
        reply->abort();
        QTimer::singleShot(0, this, SLOT(authenticationError()));
    }
}

void MainWindow::authenticationError()
{
    showMessage(tr("Authentication failed - check settings"));
}

void MainWindow::loadCurrentThumbnail()
{
    Program* p = currentProgram();
    if (p) {
        //qDebug() << "Fetching thumbnail " << p->thumbnail.toString();
        authSent = false;
        connect(http.get(QNetworkRequest(p->thumbnail)),
                SIGNAL(finished()), SLOT(thumbnailReceived()));
    }
}

void MainWindow::thumbnailReceived()
{
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
    if (reply) {
        QPixmap pixmap;
        pixmap.loadFromData(reply->readAll());
        QUrl key = reply->request().url();
        //qDebug() << "Caching pixmap " << key;
        if (QPixmapCache::insert(key.toString(), pixmap)) {
            // if we are still focused on the same program, show it again
            Program* p = currentProgram();
            if (p && p->thumbnail == key)
                showFocusedProgram();
        }
        reply->deleteLater();
    }
}

void MainWindow::updateClock()
{
    ui->clock->setText(QTime::currentTime().toString("hh:mm"));
}

void MainWindow::showMainMenu()
{
    MenuDialog menu(this);
    menu.addActionItem(ui->actionToggleFullScreen);
    menu.addActionItem(ui->actionShowSettingsMenu);
    menu.addActionItem(ui->actionQuit);
    menu.exec();
}

void MainWindow::showProgramMenu()
{
    MenuDialog menu(this);
    menu.addActionItem(ui->actionPlay);
    menu.exec();
}

void MainWindow::showSettingsMenu()
{
    // display the actions as a menu
    MenuDialog menu(this);
    menu.addSettingsItem(tr("Feed URL:"), "feed/baseUrl");
    menu.addSettingsItem(tr("Username:"), "feed/username");
    menu.addSettingsItem(tr("Password:"), "feed/password");
    menu.addSettingsItem(tr("Update interval:"), "feed/minUpdateInterval");
//    QMap<QString,QVariant> schemes;
//    schemes[tr("Dark")] = "Dark";
//    schemes[tr("Light")] = "Light";
//    schemes[tr("Blue")] = "Blue";
//    menu.addSettingsItem(tr("Color scheme:"), "ui/colorScheme", schemes);
//    QMap<QString,QVariant> resolutions;
//    resolutions[tr("HD-Ready")] = "HD-720";
//    resolutions[tr("Full-HD")] = "HD-1080";
//    menu.addSettingsItem(tr("Resolution:"), "ui/resolution", resolutions);
    menu.addActionItem(tr("Close"), &menu, SLOT(accept()));
    menu.exec();
}

void MainWindow::showAllSettingsMenu()
{
    QSettings settings;
    // display the actions as a menu
    MenuDialog menu(this);
    foreach (QString key, settings.allKeys())
        menu.addSettingsItem(key, key);
    menu.addActionItem(tr("Close"), &menu, SLOT(accept()));
    menu.exec();
}

void MainWindow::toggleFullScreen()
{
    setWindowState(windowState() ^ Qt::WindowFullScreen);
}

void MainWindow::startPlayback()
{
    Program* p = currentProgram();
    if (!p)
        return;

    QListIterator<Stream*> i(p->streams);
    Stream* preferred = 0;
    while (i.hasNext()) {
        Stream* s = i.next();
        if (player.canPlay(s->mimeType) &&
            (!preferred || s->bitrate > preferred->bitrate)) {
            preferred = s;
        }
    }
    if (!preferred) {
        QMessageBox::critical(this, tr("Error"), tr("No suitable codec found"));
        return;
    }
    qDebug() << "Playing " << preferred->url
            << "(" << preferred->mimeType << ") at "
            << preferred->bitrate << " kbps";
    ui->viewStack->setCurrentWidget(player.widget());
    player.play(preferred->url);
}

void MainWindow::playbackFinished()
{
    // restore view
    ui->viewStack->setCurrentWidget(ui->programGuide);
}

void MainWindow::showMessage(const QString& message)
{
    ui->messageLabel->setText(message);
    ui->rightSideStack->setCurrentWidget(ui->messageLabelPage);
}

void MainWindow::showProgressInfo(const QString& channelName, int channel, int numChannels)
{
    if (numChannels <= 0) {
        showMessage(tr("Updating channels..."));
    }
    else {
        showMessage(tr("Updating channel %1 (%2%)").arg(channelName)
                                                     .arg(100 * channel / numChannels));
    }
}
